﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using QFlow.Data;
using QFlow.Core;
using System.Data.Linq;
using QFlow.Windows.Forms;
using System.Windows.Forms;

namespace QFlow.Windows
{
    public class SaleOrderManager
    {
        static QFlowDbContext _Context;
        public static QFlowDbContext Context
        {
            get
            {
                return _Context;
            }
            set
            {
                _Context = value;
            }
        }

        #region [ Calculations ]

        public static double CalculateProfitPercent(double originalPrice, double currentPrice)
        {
            return (currentPrice - originalPrice) * 100 / originalPrice;
        }

        public static double CalculateProfitValue(double percent, double totalValue)
        {
            return (percent * totalValue) / 100;
        }

        public static double CalculateDiscountPercent(double value, double totalValue)
        {
            return (value / totalValue * 100);
        }

        public static double CalculateDicountValue(double percent, double totalValue)
        {
            return (totalValue * percent / 100);
        }

        public static double CaluclateItemDiscount(double originalPrice, double curentPrice)
        {
            return (originalPrice - curentPrice) * 100 / originalPrice;
        }

        public static double CalculateItemPrice(double originalPrice, double discount)
        {
            return originalPrice - (discount * originalPrice / 100);
        }

        public static double CalculateItemValue(double price, double quantity)
        {
            return price * quantity;
        }

        public static double CalculateItemValue(SaleItem item)
        {
            return item.UnitPrice * item.Quantity;
        }

        public static double CalculateOrderValue(SaleOrder order, double discount)
        {
            double value = 0;

            foreach (var item in order.SaleItems)
            {
                if (!Context.ItemTypes.IsReturned(item.ItemTypeID))
                    if (item.SaleItems.Count > 0)
                        value += CalculateItemValue(item.UnitPrice, item.Quantity - item.SaleItems[0].Quantity);
                    else value += CalculateItemValue(item);
            }
            return (value - CalculateDicountValue(discount, value));
        }

        public static double CalculateOrderValueWithoutDiscount(SaleOrder order)
        {
            double value = 0;
            foreach (var item in order.SaleItems)
            {
                if (!Context.ItemTypes.IsReturned(item.ItemTypeID))
                {
                    if (item.SaleItems.Count > 0)
                        value += CalculateItemValue(item.UnitPrice, item.Quantity - item.SaleItems[0].Quantity);
                    else value += CalculateItemValue(item);
                }
            }
            return value;
        }

        public static double CalculatePaidAmount(SaleOrder order)
        {
            double paid = 0;
            foreach (var item in order.AccountingTransactions)
            {
                if (item.Income)
                    paid += item.Amount;
                else paid -= item.Amount;
            }
            return paid;
        }

        #endregion

        /// <summary>
        /// This functions check to see if all the items are set to paid status, the order will be set to paid status
        /// </summary>
        public static void SetOrderStatus(SaleOrder order, double discount)
        {
            bool allReceived = true;
            foreach (var item in order.SaleItems)
            {
                if (!Context.ItemTypes.IsReceived(item.ItemTypeID))
                {
                    allReceived = false;
                    break;
                }
            }
            bool nonReceived = true;
            foreach (var item in order.SaleItems)
            {
                if (Context.ItemTypes.IsReceived(item.ItemTypeID))
                {
                    nonReceived = false;
                    break;
                }
            }
            //check for paid amount and set the status text
            double paidAmount = SaleOrderManager.CalculatePaidAmount(order);
            if (paidAmount > 0)
            {
                double orderAmount = SaleOrderManager.CalculateOrderValue(order, discount);
                if (allReceived && paidAmount >= orderAmount)
                {
                    order.OrderStatus = Context.OrderStatus.GetClosedStatus();
                    if (!order.IsAttached())
                        order.PaymentMethod = Context.PaymentMethods.GetCashMethod();
                }
                else if (nonReceived && paidAmount >= orderAmount)
                {
                    order.OrderStatus = Context.OrderStatus.GetPendingStatus();
                    if (!order.IsAttached())
                        order.PaymentMethod = Context.PaymentMethods.GetInAdvanceMethod();
                }
                else if (!allReceived && paidAmount < orderAmount)
                {
                    order.OrderStatus = Context.OrderStatus.GetPendingStatus();
                    order.PaymentMethod = Context.PaymentMethods.GetPendingMethod();
                }
            }
        }

        public static int CheckOrderPayments(SaleOrder order, double discount)
        {
            double paidAmount = SaleOrderManager.CalculatePaidAmount(order);
            double orderAmount = SaleOrderManager.CalculateOrderValue(order, discount);
            if (paidAmount < orderAmount)
                return -1;
            else if (paidAmount > orderAmount)
                return 1;
            return 0;
        }

        public static void GetAvailableItems(int productId, out List<PurchaseItem> availableItems, out List<double> availableQuantites)
        {
            int pendingStatusId = Context.OrderStatus.GetPendingStatus().StatusID;
            int closedStatusId = Context.OrderStatus.GetClosedStatus().StatusID;
            int receivedStatus = Context.ItemTypes.GetReceivedType().ItemTypeID;

            var result = from item in Context.PurchaseItems
                         where item.ProductID == productId
                         && item.ItemTypeID == receivedStatus
                         && ( item.PurchaseOrder.StatusID == closedStatusId || item.PurchaseOrder.StatusID == pendingStatusId)

                         orderby item.ExpirationDate ascending

                         select new
                         {
                             Item = item,
                             Quantity = (item.Quantity - (item.PurchaseItems.SingleOrDefault() == null ? 0 : item.PurchaseItems.Single().Quantity)),
                         };
            var items = result.ToArray();

            availableItems = new List<PurchaseItem>();
            availableQuantites = new List<double>();

            for (int i = 0; i < items.Length; i++)
            {
                if (items[i].Quantity - items[i].Item.Sold > 0)
                {
                    availableItems.Add(items[i].Item);
                    availableQuantites.Add(items[i].Quantity - items[i].Item.Sold);
                }
            }
        }

        public static bool CheckProductStatus(StockableProduct product, double quantity, double availableQuantity, SaleOrder order)
        {
            double sold = 0;
            foreach (var item in order.SaleItems)
            {
                if (item.ProductID == product.ProductID)
                    sold += item.PurchaseItem.Sold;
            }

            if (availableQuantity - quantity < 0)
            {
                MessageBox.Show(QFlow.Windows.Properties.Resources.MBProductShortage, QFlow.Windows.Properties.Resources.MBErrorCaption, MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
                return false;
            }
            else if (product.CurrentQuantity - sold - quantity <= product.ReorderPoint)
            {
                MessageBox.Show(QFlow.Windows.Properties.Resources.MBProductLimit, QFlow.Windows.Properties.Resources.MBWarningCaption, MessageBoxButtons.OK, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1);
            }
            return true;
        }

        #region [ Items ]

        public static void CreateSaleItem(double quantity, double price, StockableProduct product,
            double discount, double profit, double taxes, string notes, PurchaseItem purchaseItem, SaleOrder order)
        {

            SaleItem item = new SaleItem()
            {
                Quantity = quantity,
                UnitPrice = price,
                StockableProduct = product,
                Timestamp = DateTime.Now,
                DiscountPercent = discount,
                ProfitPercent = profit,
                TaxesPercent = taxes,
                ItemType = Context.ItemTypes.GetPendingType(),
                Notes = !string.IsNullOrEmpty(notes) ? notes : null,
                PurchaseItem = purchaseItem,
            };
            order.SaleItems.Add(item);

            purchaseItem.Sold += quantity;
        }

        public static void UpdateSaleItem(SaleItem item, double quantity, double price, StockableProduct product,
            double discount, double profit, double taxes, string notes, PurchaseItem purchaseItem)
        {
            if (Context.ItemTypes.IsReceived(item.ItemTypeID))
            {
                //add the item's old quantity
                item.StockableProduct.CurrentQuantity += item.Quantity;
            }
            item.PurchaseItem.Sold -= item.Quantity;

            item.Quantity = quantity;
            item.UnitPrice = price;
            item.StockableProduct = product;
            item.ProfitPercent = profit;
            item.TaxesPercent = taxes;
            item.DiscountPercent = discount;
            item.PurchaseItem = purchaseItem;

            item.User = Authentication.CurrentUser as User;

            if (!string.IsNullOrEmpty(notes))
                item.Notes = notes;

            item.Timestamp = DateTime.Now;

            if (Context.ItemTypes.IsReceived(item.ItemTypeID))
            {
                //remove the item's new quantity
                item.StockableProduct.CurrentQuantity -= item.Quantity;
            }
            item.PurchaseItem.Sold += item.Quantity;
        }

        public static void RemoveSaleItem(SaleItem item, SaleOrder order)
        {
            if (Context.ItemTypes.IsReceived(item.ItemTypeID))
            {
                item.StockableProduct.CurrentQuantity += item.Quantity;
            }
            item.PurchaseItem.Sold -= item.Quantity;

            if (item.IsAttached())
                order.SaleItems.Remove(item);
            else
            {
                Context.RemoveRecordFromInsertions(item);
                order.SaleItems.Remove(item);
            }
        }

        public static void ReceiveItem(SaleItem item)
        {
            item.ItemType = Context.ItemTypes.GetReceivedType();
            item.StockableProduct.CurrentQuantity -= item.Quantity;
        }

        public static void ReceiveItems(SaleOrder order)
        {
            foreach (var item in order.SaleItems)
            {
                if (Context.ItemTypes.IsPending(item.ItemTypeID))
                {
                    item.ItemType = Context.ItemTypes.GetReceivedType();
                    item.StockableProduct.CurrentQuantity -= item.Quantity;
                }
            }
        }

        #endregion

        #region [ Payments ]

        public static void CreatePayment(double amount, Account selectedAccount, SaleOrder order)
        {
            AccountingTransaction transaction = new AccountingTransaction()
            {
                Amount = amount,
                Income = true,
                Account1 = selectedAccount,
                TimeStamp = DateTime.Now
            };

            order.AccountingTransactions.Add(transaction);

            Accounting.SalesCredit += amount;
            transaction.Account1.Amount += transaction.Amount;
        }

        public static void UpdatePayment(AccountingTransaction transaction, double amount, Account selectedAccount, SaleOrder order)
        {
            Accounting.SalesCredit -= transaction.Amount;
            transaction.Account1.Amount -= transaction.Amount;

            transaction.Amount = amount;
            transaction.TimeStamp = DateTime.Now;
            transaction.Account1 = selectedAccount;

            Accounting.SalesCredit += amount;
            transaction.Account1.Amount += amount;
        }

        public static void DeletePayment(AccountingTransaction transaction, SaleOrder order)
        {
            Accounting.SalesCredit -= transaction.Amount;
            transaction.Account1.Amount -= transaction.Amount;

            Context.RemoveRecordFromInsertions(transaction);
            order.AccountingTransactions.Remove(transaction);
        }

        public static void PayOrder(SaleOrder order, double discount)
        {
            double oldpaidAmount = 0;
            foreach (var item in order.AccountingTransactions)
            {
                if (item.IsAttached())
                {
                    if (item.Income == true)
                        oldpaidAmount += item.Amount;
                    else oldpaidAmount -= item.Amount;
                }
            }
            double orderValue = CalculateOrderValue(order, discount);

            NewPayment frm = new NewPayment(Context, true, orderValue - oldpaidAmount);
            if (frm.ShowDialog() == DialogResult.OK)
            {
                if (oldpaidAmount < orderValue)
                {
                    for (int i = order.AccountingTransactions.Count - 1; i >= 0; i--)
                    {
                        var item = order.AccountingTransactions[i];
                        if (!item.IsAttached())
                        {
                            Accounting.SalesCredit -= item.Amount;
                            item.Account1.Amount -= item.Amount;

                            order.AccountingTransactions.Remove(item);
                        }
                    }
                }
                CreatePayment(frm.Amount, frm.SelectedAccount, order);
            }
        }

        #endregion

        public static void CreateSaleOrder(SaleOrder order, double discount, Customer customer)
        {
            order.Date = DateTime.Now;
            order.DiscountPercent = discount;
            order.Customer = customer;

            foreach (var item in order.SaleItems)
            {
                item.Timestamp = DateTime.Now;
                item.User = Context.Users.GetCurrentUser();
            }

            foreach (var item in order.AccountingTransactions)
            {
                item.Account = customer.Accounts.First();
                //add the amount to the customer
                item.Account.Amount += item.Amount;

                item.TimeStamp = DateTime.Now;
                item.User = Context.Users.GetCurrentUser();
            }

            //cut the order's value from the customer's credit
            customer.Accounts.First().Amount -= CalculateOrderValue(order, order.DiscountPercent);

            Context.SaleOrders.InsertOnSubmit(order);
            Context.SubmitChanges();
        }

        public static void UpdateSaleOrder(SaleOrder order, double discount, Customer customer)
        {
            UpdateCustomerCredit(order, discount, customer);

            foreach (var item in order.SaleItems)
            {
                if (!item.IsAttached())
                {
                    item.Timestamp = DateTime.Now;
                    item.User = Context.Users.GetCurrentUser();
                }
            }

            foreach (var item in order.AccountingTransactions)
            {
                if (!item.IsAttached())
                {
                    item.Account = order.Customer.Accounts.First();
                    //add the amount to the customer
                    item.Account.Amount += item.Amount;

                    item.TimeStamp = DateTime.Now;
                    item.User = Context.Users.GetCurrentUser();
                }
            }
            Context.SubmitChanges();
        }

        private static void UpdateCustomerCredit(SaleOrder order, double discount, Customer customer)
        {
            QFlowDbContext context = new QFlowDbContext();
            SaleOrder oldOrder = context.SaleOrders.Single(o => o.SaleOrderID == order.SaleOrderID);
            order.Customer.Accounts.First().Amount += CalculateOrderValue(oldOrder, oldOrder.DiscountPercent);

            order.Customer = customer;
            order.DiscountPercent = discount;

            order.Customer.Accounts.First().Amount -= CalculateOrderValue(order, order.DiscountPercent);
        }

        public static void CancelSaleOrder(SaleOrder order)
        {
            double paidAmount = CalculatePaidAmount(order);
            if (paidAmount > 0)
            {
                NewPayment frm = new NewPayment(Context, false, paidAmount);
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    AccountingTransaction transaction = new AccountingTransaction()
                    {
                        Amount = frm.Amount,
                        Income = false,
                        Account1 = order.Customer.Accounts.First(),
                        Account = frm.SelectedAccount,
                        User = Context.Users.GetCurrentUser(),
                        TimeStamp = DateTime.Now,
                    };

                    order.AccountingTransactions.Add(transaction);

                    Accounting.SalesReturn += transaction.Amount;
                    //system account
                    transaction.Account.Amount -= transaction.Amount;

                    foreach (var item in order.SaleItems)
                    {
                        if (Context.ItemTypes.IsReceived(item.ItemTypeID))
                        {
                            //set the current quantity of the sold product
                            item.StockableProduct.CurrentQuantity += item.Quantity;
                            item.PurchaseItem.Sold -= item.Quantity;
                        }
                        else if (Context.ItemTypes.IsReturned(item.ItemTypeID))
                        {
                            //set the current quantity of the sold product
                            item.StockableProduct.CurrentQuantity -= item.Quantity;
                            item.PurchaseItem.Sold += item.Quantity;
                        }
                    }

                    order.Date = DateTime.Now;
                    order.OrderStatus = Context.OrderStatus.GetCanceledStatus();
                    Context.SubmitChanges();
                }
            }
            else
            {
                foreach (var item in order.SaleItems)
                {
                    if (Context.ItemTypes.IsReceived(item.ItemTypeID))
                    {
                        //set the current quantity of the sold product
                        item.StockableProduct.CurrentQuantity += item.Quantity;
                        item.PurchaseItem.Sold -= item.Quantity;
                    }
                    else if (Context.ItemTypes.IsReturned(item.ItemTypeID))
                    {
                        //set the current quantity of the sold product
                        item.StockableProduct.CurrentQuantity -= item.Quantity;
                        item.PurchaseItem.Sold += item.Quantity;
                    }
                }

                order.Date = DateTime.Now;
                order.OrderStatus = Context.OrderStatus.GetCanceledStatus();
                Context.SubmitChanges();
            }
        }

        #region [ Return Sale Order ]

        public static SaleItem ReturnItem(SaleItem item, double quantity, double orderDiscount)
        {
            SaleItem returned = item.Clone();
            returned.ItemType = Context.ItemTypes.GetReturnedType();
            returned.User = Context.Users.GetCurrentUser();
            returned.SaleItem1 = item;
            returned.UnitPrice = item.UnitPrice - CalculateDicountValue(orderDiscount, item.UnitPrice);
            return returned;
        }

        public static bool CheckReturnItems(List<SaleItem> returnItems, SaleItem item)
        {
            foreach (var returnItem in returnItems)
            {
                if (returnItem.ParentSaleItemID == item.SaleItemID)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Calculates the maximum return quantity for this item.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="returnItems"></param>
        /// <returns></returns>
        public static double GetMaximumReturnQuantity(SaleItem item)
        {
            double quantity = 0;
            quantity += item.Quantity;
            return quantity;
        }

        public static double GetAvailableReturnQuantity(SaleItem returnedItem)
        {
            SaleItem item = returnedItem.SaleItem1;
            double quantity = 0;
            quantity += item.Quantity;

            return quantity;
        }

        public static double GetMinimumReturnQuantity(SaleItem returndItem)
        {
            if (!returndItem.IsAttached())
            {
                return 0.1;
            }
            else
            {
                return GetOriginalQuantity(returndItem.SaleItemID);
            }
        }

        public static double GetOriginalQuantity(int saleItemId)
        {
            return new QFlowDbContext().SaleItems.Where(item => item.SaleItemID == saleItemId).Select(item => item.Quantity).First();
        }

        public static double SaveReturenedOrder(SaleOrder order, List<SaleItem> returnedItems)
        {
            double returnedValue = 0;
            foreach (var item in returnedItems)
            {
                if (!item.IsAttached())
                {
                    order.SaleItems.Add(item);
                    returnedValue += CalculateItemValue(item);
                    item.Timestamp = DateTime.Now;

                    item.StockableProduct.CurrentQuantity += item.Quantity;
                    item.PurchaseItem.Sold -= item.Quantity;
                }
                else if (item.IsDirty())
                {
                    double quantity = item.Quantity - GetOriginalQuantity(item.SaleItemID);
                    returnedValue += CalculateItemValue(item.UnitPrice, quantity);
                    item.Timestamp = DateTime.Now;

                    item.StockableProduct.CurrentQuantity += quantity;
                    item.PurchaseItem.Sold -= quantity;
                }

            }
            if (returnedValue > 0)
            {
                NewPayment frm = new NewPayment(Context, false, returnedValue);
                if (frm.ShowDialog() == DialogResult.OK)
                {
                    AccountingTransaction transaction = new AccountingTransaction()
                    {
                        Amount = frm.Amount,
                        Income = false,
                        Account1 = order.Customer.Accounts.First(),
                        Account = frm.SelectedAccount,
                        User = Context.Users.GetCurrentUser(),
                        TimeStamp = DateTime.Now,
                    };

                    order.AccountingTransactions.Add(transaction);

                    Accounting.SalesReturn += transaction.Amount;
                    transaction.Account.Amount -= transaction.Amount;

                    Context.SubmitChanges();
                }
            }
            return returnedValue;
        }

        #endregion
    }
}
